/* Map.cs

	Infinite Rogue
	By Dan Alexander, Andy Law
	July 2012

	Cross platform roguelike using C#, Mono, WinForms and GDI.
	
	LICENSE: New BSD License
*/

using System;
using System.Drawing;
using System.IO;
using System.Collections.Generic;

public class Map{

	public int Width { get;set; }
	public int Height { get;set; }
	public int LevelNumber { get; set; }

	private Tile[,] tileMap;

	public Map(int width, int height, char[,] charMap){
		LevelNumber = 1;
		Width = width;
		Height = height;
		tileMap = new Tile[Width,Height];
			
			for (int i = 0; i < Height; ++i)
			{
				for (int j = 0; j < Width; ++j)
				{
                    tileMap[j, i] = new Tile();
					tileMap[j,i].Character = charMap[j,i];

					if (charMap[j,i] == '#')
					{
						tileMap[j,i].Color = Color.Gray;
						tileMap[j,i].Passable = false;
                        tileMap[j, i].Type = TileType.Wall;
					}
					else if (charMap[j,i] == '.')
					{
						tileMap[j,i].Passable = true;
                        tileMap[j, i].Type = TileType.Floor;
					}
				}
			}
			
			//make the doors
			for (int i = 0; i < Height; ++i)
			{
				for (int j = 0; j < Width; ++j)
				{
					if (DoorCheck(j,i))
					{
                        tileMap[j, i] = new Tile()
                            {
                                Character = '+',
                                Color = Color.SaddleBrown,
                                Discovered = false,
                                DoorStatus = Tile.DoorState.Closed,
                                Passable = false,
                                Type = TileType.ClosedDoor
                            };
					}
				}
			}
	}

    public MapCoord Upstairs { get; set; }
    public MapCoord Downstairs { get; set; }
	
	public void AddStairs(int[] coords){
		int startX = coords[0];
		int startY = coords[1];
		int endX = coords[2];
		int endY = coords[3];
		
		if (LevelNumber > 1)
		{
			//only do stairs out for levels higher than one
			tileMap[startX,startY].Passable = true;
			tileMap[startX,startY].Character = '<';
			tileMap[startX,startY].Color = Color.White;
            tileMap[startX, startY].Type = TileType.StairUp;
            Upstairs = new MapCoord() { Level = LevelNumber, x = startX, y = startY };
		}
		
		tileMap[endX,endY].Passable = true;
		tileMap[endX,endY].Character = '>';
		tileMap[endX,endY].Color = Color.White;
        tileMap[endX, endY].Type = TileType.StairDown;
        Downstairs = new MapCoord() { Level = LevelNumber, x = endX, y = endY };
	}

    public MapCoord PlaceCharacterOnMap()
    {
        int x = Utilities.GlobalRandom.Next(Width);
        int y = Utilities.GlobalRandom.Next(Height);

        while (tileMap[x, y].Passable == false)
        {
            x = Utilities.GlobalRandom.Next(Width);
            y = Utilities.GlobalRandom.Next(Height);
        }

        return new MapCoord()
        {
            x = x,
            y = y,
            Level = LevelNumber
        };
    }

    public void OpenDoor(int x, int y)
    {
        tileMap[x, y].OpenDoor();
    }

    public Tile GetTile(int x, int y)
    {
        if (x < 0 || x >= Width || y < 0 || y >= Height) return new Tile('#', false) { Discovered = false, Passable = false, Type = TileType.Wall };
        return tileMap[x, y];
    }
	
	//is the player currently on a stairway up
	public bool StairUp(int x, int y)
    {
        return tileMap[x, y].Type == TileType.StairUp;
	}
	
	//is the player currently on a stairway down
    public bool StairDown(int x, int y)
    {
        return tileMap[x, y].Type == TileType.StairDown;
    }

	private bool DoorCheck(int x, int y)
	{
		//check this tile
		if (GetTile(x, y).Type != TileType.Floor)
			return false; //we aren't a floor

        if (GetTile(x - 1, y).Type == TileType.Wall && GetTile(x + 1, y).Type == TileType.Wall)
        {
            if (GetTile(x, y + 1).Type == TileType.Floor && GetTile(x - 1, y + 1).Type == TileType.Floor && GetTile(x + 1, y + 1).Type == TileType.Floor)
            {
                return true;
            }
            else if (GetTile(x, y - 1).Type == TileType.Floor && GetTile(x - 1, y - 1).Type == TileType.Floor && GetTile(x + 1, y - 1).Type == TileType.Floor)
            {
                return true;
            }
        }
        else if (GetTile(x, y - 1).Type == TileType.Wall && GetTile(x, y + 1).Type == TileType.Wall)
        {
            if (GetTile(x + 1, y).Type == TileType.Floor && GetTile(x + 1, y - 1).Type == TileType.Floor && GetTile(x + 1, y + 1).Type == TileType.Floor)
            {
                return true;
            }
            else if (GetTile(x - 1, y).Type == TileType.Floor && GetTile(x - 1, y - 1).Type == TileType.Floor && GetTile(x - 1, y + 1).Type == TileType.Floor)
            {
                return true;
            }
        }

        return false;
	}

	public void UpdateDiscoveredTiles(Character c){
		for (int x = 0; x < Width; ++x){
			for (int y = 0; y < Height; ++y){
				CanBeSeen(c,x,y);
			}
		}
	}
	
	public bool CanBeSeen(Character _player, int x1, int y1)
	{
		//int rise = Math.Abs(y1 - _player.Y);
		//int run = Math.Abs(x1 - _player.X);
		
		//first, check for distance
		//int distance = Distance(x1,y1,_player.X,_player.Y);
		
		
		//check against find distance
		//if (distance > Player.info.FindDistance)
			//return false;
		
		//check against view distance (how far away can we discover new tiles)
		//if (distance > Player.info.ViewDistance)
			//return false;
		
		//now, check LOS, using some raycasting style checks
		
		//if (temp)
			//return true;
		
		int x = x1;
		int y = y1;
		
		while (x != _player.X || y != _player.Y)
		{
			if (x < _player.X)
				++x;
			else if (x > _player.X)
				--x;
			if (y < _player.Y)
				++y;
			else if (y > _player.Y)
				--y;
			
			if (GetTile(x, y).Passable == false){
				return false;
			}
		}
		
		/*
		if (rise == run)
		{
			int x = x1;
			int y = y1;
			int dx = 1;
			int dy = 1;
			
			if (x1 > Player.info.X)
				dx = -1;
			if (y1 > Player.info.Y)
				dy = -1;
			
			while (x != Player.info.X && y != Player.info.Y)
			{
				x += dx;
				y += dy;
				
				if (GetTile(x, y).passable == false)
					return false;
			}
		}
		else
		{
			return false;
		}*/
		
		tileMap[x1,y1].Discovered = true;
		_player.DiscoveredTiles.Add(tileMap[x1,y1].Tag);
		
		return true;
	}
	
	public static int Distance(int x1, int y1, int x2, int y2)
	{
		//Pythagorian's Theorem
        int lenX = x1 - x2;
        int lenY = y1 - y2;
        return (int)Math.Sqrt(lenY * lenY + lenX * lenX);
	}

	public MapCoord DrawStart(Character _player, int width, int height){
		int startX = Math.Max(_player.X-width/2,0);
		int startY = Math.Max(_player.Y-height/2,0);
		
		if (startX+width >= Width)
		{
			startX = Width - width;
		}
		if (startY+height >= Height)
		{
			startY = Height - height;
		}

        return new MapCoord() { x = startX, y = startY, Level = LevelNumber };
	}
	
	public char[,] SectionAroundPlayer(Character _player, int width, int height)
	{
		char[,] section = new char[width,height];
		
		int startX = Math.Max(_player.X-width/2,0);
		int startY = Math.Max(_player.Y-height/2,0);
		
		if (startX+width >= Width)
		{
			startX = Width - width;
		}
		if (startY+height >= Height)
		{
			startY = Height - height;
		}
		
		int xx = 0;
		int yy = 0;
		
		for (int y = startY; y < startY+height; ++y)
		{
			for (int x = startX; x < startX + width; ++x)
			{
				section[xx,yy] = GetTile(x, y).Character;
				if (x == _player.X && y == _player.Y){
					section[xx,yy] = _player.Tile.Character;
				}
				++xx;
			}
			xx = 0;
			++yy;
		}
		
		return section;
	}
	
	public char characterAt(int x, int y){
		return GetTile(x, y).Character;
	}

	public void SaveToFile(string filename)
		{
			StreamWriter sw = new StreamWriter(filename);
			
			for (int y = 0; y < Height; ++y)
			{
				for (int x = 0; x < Width; ++x)
				{
					sw.Write(GetTile(x, y).Character);
				}
				sw.WriteLine();
			}
			
			sw.Close();
		}
	
} //end of class

	public struct MapCoord
	{
		public int x;
		public int y;
        public int Level;

        public bool InTarget(MapCoord center, int radius)
        {
            return center.Level == Level && x.Between(center.x - radius, center.x + radius) && y.Between(center.y - radius, center.y + radius);
        }

        public static MapCoord operator +(MapCoord a, Tuple<int, int> b)
        {
            return new MapCoord()
            {
                Level = a.Level,
                x = a.x + b.Item1,
                y = a.y + b.Item2
            };
        }

        public static Tuple<int, int> operator-(MapCoord a, MapCoord b)
        {
            return Tuple.Create(a.x - b.x, a.y - b.y);
        }

        public static int Distance(int x1, int y1, int x2, int y2)
        {
            //Pythagorian's Theorem
            int lenX = x1 - x2;
            int lenY = y1 - y2;
            return (int)Math.Sqrt(lenY * lenY + lenX * lenX);
        }

        public static int Distance(MapCoord a, MapCoord b)
        {
            return Distance(a.x, a.y, b.x, b.y);
        }

        public override string ToString()
        {
            return string.Format("({0},{1},{2})", x, y, Level);
        }
	}